
from functools import partial
from multiprocessing import Manager
import os
import threading
from dotenv import load_dotenv
from pymysql import OperationalError
from sanic import Sanic
from loguru import logger
from sanic_ext import Extend
from sqlalchemy import create_engine, text
from werkzeug.utils import ImportStringError, find_modules, import_string
import yaml

from app.core import locked, singleton
from app.core.exception import initException
from app.utils.RedisUtils import RedisUtil
import ujson
from app.utils.Idgenerator import options, generator


@singleton
class App:

    app=None

    config=None

    dbEngine=None

    logger=None

    redis:RedisUtil

    lock = threading.Lock()

    idGenerator = None

    appPath=""

    projectPath=""

    @locked
    def createApp(self,configName=None):
        
        from app.middleware import initMiddleware
        """创建sanic app

        :param config_name: 默认config_name='Dev'，使用Dev配置
        :return: app -> Sanic
        """

        self.appPath=os.path.dirname(os.path.abspath(__file__))
        
        self.projectPath=os.path.dirname(self.appPath)

        if not self.app is None:
            return self.app

        self.config=self.initConfig(configName)

        dumps = partial(ujson.dumps, escape_forward_slashes=False)

        app=Sanic(self.config["app"]["name"],dumps=dumps)

        app.config.update(self.config)

        self.initLog()

        app.static("/public","public")
        
        self.registerBlueprints("app.controllers",app)

        self.registerBlueprints("app.websocket",app)

        self.initDb()

        self.initRedis()

        self.initIdGenerator()

        initMiddleware(app)

        initException(app)

        self.app=app

        return app
    
    def initIdGenerator(self):
        idGeneratorConfig=self.config['idGenerator']
        idOptions=options.IdGeneratorOptions(worker_id=idGeneratorConfig['WorkerId'],
                                             worker_id_bit_length=idGeneratorConfig['WorkerIdBitLength'],
                                             seq_bit_length=idGeneratorConfig['SeqBitLength'])
        idgen=generator.DefaultIdGenerator()
        idgen.set_id_generator(idOptions)
        self.idGenerator=idgen

    def initRedis(self):
        redisConfig=self.config['redis']
        self.redis=RedisUtil(host=redisConfig['host'],port=redisConfig['port'],db=redisConfig['db'],password=redisConfig['password'])
        try:
            self.redis.isConnection()
        except Exception as e:
            self.logger.warning("Redis服务连接失败！{}".format(e.args))
        
    def initDb(self):
        try:
            dbConfig=self.config['db']
            connStr="{}+{}://{}:{}@{}:{}/{}".format(dbConfig['type'],dbConfig['driver'],dbConfig['user'],
                                                    dbConfig['pass'],dbConfig['host'],dbConfig['port'],dbConfig['dbname'])
            self.logger.info("正在数据库"+connStr)
            self.dbEngine=create_engine(connStr, echo=dbConfig['debug']==True,pool_recycle=dbConfig['poolRecycle'],
                                        pool_size=dbConfig['poolSize'],pool_pre_ping=True,max_overflow=dbConfig['maxOverflow'],
                                        pool_timeout=dbConfig['poolTimeout'])
            self.dbEngine.connect()
        except Exception as e:
            self.logger.error("数据库连接失败！连接字符串为："+connStr+" 源错误：{}".format(e.args))
            raise SystemExit
        

    def registerBlueprints(self,root,app:Sanic,prefix="/"):

        for name in find_modules(root,recursive=True):
            mod=import_string(name)
            if hasattr(mod,'bp'):
                logger.info("{},api初始化完成".format(name))
                app.blueprint(mod.bp)

    def initConfig(self,configName=""):
        defaultConfig="app/config/app.yaml"
        with open(defaultConfig) as f:
            yamlData=yaml.load(f,Loader=yaml.FullLoader)
        if configName!=None:
            configName=configName.replace(" ","")
            if(configName!=""):
                filename="app-"+configName
                configFile="app/config/{}.yaml".format(filename)
                with open(configFile) as f:
                    configData=yaml.load(f,Loader=yaml.FullLoader)
                yamlData.update(configData)
                logger.info("成功读取配置文件！{}".format(configFile))
        yamlData=self.envDataUpdateConfig(yamlData)
        return yamlData

    def envDataUpdateConfig(self,config:dict):
        from app.utils.MyUtils import update_nested_dict
        success=load_dotenv(os.getcwd()+"/.env")
        if not success:
            return config
        configContrast={
            "APP_DEBUG":"app.debug",
            "SERVER_HOST":"server.host",
            "SERVER_NAME":"app.name",
            "SERVER_PORT":"server.port",
            "SERVER_HTTPS":"server.https",
            "DB_HOST":"db.host",
            "DB_PORT":"db.port",
            "DB_USER":"db.user",
            "DB_PASS":"db.pass",
            "DB_NAME":"db.dbname",
            "DB_TYPE":"db.type",
            "DB_DRIVER":"db.driver",
            "REDIS_HOST":"redis.host",
            "REDIS_PORT":"redis.port",
            "REDIS_PASS":"redis.password",
            "REDIS_DB":"redis.db",
            "ALIYUN_ACCESS_KEY":"aliyun.accessKey",
            "ALIYUN_ACCESS_SECRET":"aliyun.accessSecret"
        }
        for key in os.environ:
            if key not in configContrast.keys():
                continue
            if os.environ[key]=="" or os.environ[key]==None:
                continue
            logger.warning("读取环境变量{}，值为{}".format(key,os.environ[key]))
            update_nested_dict(config,configContrast[key].split("."),os.environ[key])
        return config
            
    def initLog(self):
        root_path = os.path.abspath(os.path.dirname(__file__)).split('applications')[0]
        file_name=self.config["logging"]["file_name"]
        log_path = os.path.join(root_path, 'logs', file_name)
        logger.info(log_path)
        # 每天00:01会创建一个新的文件
        logger.add(log_path,rotation="00:01")
        self.logger=logger
        return logger
    
    def run(self):
        serverConfig=self.app.config['server']
        appConfig=self.app.config['app']
        ssl=None
        if(serverConfig['https']=="Y"):
            ssl=self.projectPath+"/ssl/"
        Extend(self.app)
        self.app.run(host=serverConfig['host'],port=int(serverConfig['port']),ssl=ssl,debug=appConfig['debug']=='Y')
        

myApp=App()